﻿
using Boilen.Primitives.Members;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;


namespace Boilen.Primitives.Implementers {

    /// <inheritdoc/>
    /// <summary>
    /// Implements a dependency property.
    /// </summary>
    public abstract class DependencyProperty<T> : ValueAccessor<T>, FreezableImplementer.ITarget {

        private readonly string changeCallbackName_;
        private readonly string coerceCallbackName_;
        private readonly string validateCallbackName_;

        private readonly string dependencyPropertyTypeName_;
        private readonly string dependencyPropertyKeyTypeName_;

        private Type attachedTargetType_;


        /// <inheritdoc/>
        public override string FieldName { get { return this.AccessorName + MemberKind; } }

        /// <inheritdoc/>
        public override string FieldTypeName { get { return this.dependencyPropertyTypeName_; } }

        /// <inheritdoc/>
        public override string FieldModifiers { get { return GetAccessibilityValue( this.Accessibility ) + " " + this.StandardDependencyPropertyModifiers; } }

        /// <summary>
        /// Gets or sets a value indicating whether this is an attached dependency property.
        /// </summary>
        public bool Attached { get; set; }

        /// <summary>
        /// Gets or sets an attached property's target type. Default is <see cref="UIElement"/>.
        /// </summary>
        public Type AttachedTargetType {
            get { return this.attachedTargetType_ ?? typeof( UIElement ); }
            set {
                Ensure.ArgSatisfies( value == null || typeof( DependencyObject ).IsAssignableFrom( value ), "value", "Target type must derive from DependencyObject: {0}", value );
                this.attachedTargetType_ = value;
            }
        }

        /// <summary>
        /// Gets or sets the accessibility of the dependency property setter accessor.
        /// Any value other than <see cref="Accessibility.Public"/> will create a read-only property.
        /// </summary>
        public Accessibility SetterAccessibility { get; set; }

        /// <summary>
        /// Gets a value indicating whether this is a read-only dependency property.
        /// </summary>
        public bool IsReadOnly { get { return this.SetterAccessibility != Accessibility.Public; } }


        /// <summary>
        /// Gets or sets the options used to construct the dependency property's <see cref="FrameworkPropertyMetadata"/>.
        /// </summary>
        public FrameworkPropertyMetadataOptions Options { get; set; }

        /// <summary>
        /// Gets or sets the change handler to use instead of the <see cref="FrameworkPropertyMetadataOptions"/> compatibility implementation on Silverlight.
        /// </summary>
        public string SilverlightOptionsOverride { get; set; }

        /// <summary>
        /// Gets or sets a value used to determine how the dependency property value is coerced.
        /// </summary>
        public Coerce Coerce { get; set; }

        /// <summary>
        /// Gets or sets a value used to determine how the dependency property value is validated.
        /// </summary>
        public Validate Validate { get; set; }

        /// <summary>
        /// Gets or sets a value used to determine whether a change handler is registered for the dependency property.
        /// </summary>
        public Changed Changed { get; set; }

        /// <summary>
        /// Gets or sets the name of the handler used when <see cref="Coerce"/> is <see cref="Boilen.Coerce.Custom"/>.
        /// </summary>
        public string CoerceHandler { get; set; }

        /// <summary>
        /// Gets or sets the name of the handler used when <see cref="Validate"/> is <see cref="Boilen.Validate.Custom"/>.
        /// </summary>
        public string ValidateHandler { get; set; }

        /// <summary>
        /// Gets or sets the name of the handler used when <see cref="Changed"/> is not <see cref="Boilen.Changed.None"/>.
        /// </summary>
        public string ChangedHandler { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the dependency property is based on an existing dependency property.
        /// </summary>
        internal bool FromExistingProperty { get; set; }


        /// <summary>
        /// Gets the standard modifiers for dependency property fields.
        /// </summary>
        protected string StandardDependencyPropertyModifiers { get { return "static readonly"; } }

        /// <summary>
        /// Gets the suffix for the <see cref="DependencyPropertyKey"/> field, or an empty string if the property is not <see cref="IsReadOnly"/>.
        /// </summary>
        protected string DependencyPropertyKeySuffix { get { return this.IsReadOnly ? "Key" : ""; } }

        /// <summary>
        /// Gets the name of the <see cref="DependencyPropertyKey"/> field, or the <see cref="DependencyProperty"/> field if the property is not <see cref="IsReadOnly"/>.
        /// </summary>
        protected string DependencyPropertyKeyFieldName { get { return this.FieldName + this.DependencyPropertyKeySuffix; } }

        /// <summary>
        /// Gets the field accessor for the <see cref="DependencyProperty"/> object used to observe the property value.
        /// </summary>
        protected string DependencyPropertyObserveName { get { return this.Parent.TypeNamePrefix + "." + this.FieldName; } }

        /// <summary>
        /// Gets the field accessor for the <see cref="DependencyProperty"/> or <see cref="DependencyPropertyKey"/> object used to update the property value.
        /// </summary>
        protected string DependencyPropertyUpdateName { get { return this.DependencyPropertyObserveName + this.DependencyPropertyKeySuffix; } }

        /// <summary>
        /// Gets the name of the shim property changed event handler for dependency properties.
        /// </summary>
        protected string ShimChangedAction { get { return this.ChangedAction + "Shim"; } }

        /// <summary>
        /// Gets the name of the subproperty changed event handler for freezable silverlight dependency properties.
        /// </summary>
        protected string SilverlightSubpropertyChangedAction { get { return this.GetActionName( "SubpropertyChanged" ) + "_SL"; } }

        /// <summary>
        /// Gets the name of the field containing the dependency property value reversion flag used by the freezable silverlight helper.
        /// </summary>
        protected string SilverlightRevertingValueFieldName { get { return Property<bool>.PropertyFieldNamePrefix + this.AccessorName + "_RevertingDependencyPropertyChange_SL"; } }

        /// <summary>
        /// Gets a value indicating whether the declaring type should be considered as freezable in the silverlight implementation.
        /// </summary>
        internal bool IsParentFreezable { get; set; }

        /// <summary>
        /// Gets a value indicating whether the property type should be considered as freezable in the silverlight implementation.
        /// </summary>
        protected bool IsPropertyFreezable {
            get { return !this.Attached && (typeof( Freezable ).IsAssignableFrom( this.Type ) || this.Type.IsInterface); }
        }

        /// <summary>
        /// Gets a value indicating whether a silverlight freezable helper is required.
        /// </summary>
        private bool IsSilverlightPropertyFreezeHelperRequired {
            get { return this.IsPropertyFreezable && !this.Type.Namespace.StartsWith( "System" ); }
        }

        /// <summary>
        /// Gets a value indicating whether a silverlight freezable helper is required.
        /// </summary>
        private bool IsSilverlightStyleFreezeHelperRequired {
            get { return this.IsSilverlightPropertyFreezeHelperRequired && this.Parent.IsStyleable; }
        }

        /// <summary>
        /// Gets a value indicating whether a silverlight freezable helper is required.
        /// </summary>
        protected bool IsSilverlightFreezableHelperRequired {
            get { return this.IsPropertyFreezable && (this.Changed != Changed.None || this.IsParentFreezable); }
        }

        /// <summary>
        /// Gets a value indicating whether a field to flag whether the dependency property value is being reverted is required by the silverlight freezable helper.
        /// </summary>
        protected bool IsSilverlightRevertingValueFieldRequired {
            get { return !this.Attached && (this.Validate != Validate.None || this.IsParentFreezable); }
        }

        /// <summary>
        /// Gets a value indicating whether a <see cref="FrameworkPropertyMetadataOptions"/> value is implemented in a silverlight change helper.
        /// </summary>
        protected bool IsSilverlightMetadataHelperRequired {
            get {
                return !this.Condition.Equals( CompilationSymbol.NotSilverlight )
                    && this.Options != FrameworkPropertyMetadataOptions.None;
            }
        }

        /// <summary>
        /// Gets a value indicating whether a desktop change helper is required.
        /// </summary>
        protected bool IsChangedCallbackShimRequired {
            get {
                return this.Changed != Changed.None
                    && this.Changed != Changed.Static;
            }
        }

        /// <summary>
        /// Gets a value indicating whether a silverlight change helper is required.
        /// </summary>
        protected bool IsSilverlightShimRequired {
            get {
                return this.IsSilverlightMetadataHelperRequired
                    || this.IsParentFreezable
                    || this.IsSilverlightStyleFreezeHelperRequired
                    || this.IsSilverlightFreezableHelperRequired
                    || this.Coerce != Coerce.None
                    || this.Validate != Validate.None;
            }
        }

        /// <summary>
        /// Gets a value indicating whether a silverlight dependency property initializer is required.
        /// </summary>
        protected bool IsSilverlightInitializerRequired {
            get {
                return this.IsSilverlightShimRequired
                    || this.FromExistingProperty
                    || this.IsSilverlightFreezableHelperRequired
                    || this.IsReadOnly
                    || this.Options != FrameworkPropertyMetadataOptions.None
                    || (this.Changed != Changed.None && this.Changed != Changed.Static)
                    || this.DefaultValues.Any( v => v.Key.Equals( CompilationSymbol.Silverlight ) );
            }
        }

        /// <summary>
        /// Gets a value indicating whether changes to the property affect the parent object.
        /// </summary>
        protected bool AffectsParent {
            get { return Flags( FrameworkPropertyMetadataOptions.AffectsParentMeasure ) || Flags( FrameworkPropertyMetadataOptions.AffectsParentArrange ); }
        }


        /// <inheritdoc/>
        protected override IEnumerable<FieldMember> Fields {
            get {
                AttributeMember staticGenericMemberAttribute = this.Parent.Type.IsGenericType
                    ? AttributeMember.SuppressStaticMembersInGenericTypes
                    : AttributeMember.EmptyAttribute;

                if( this.IsReadOnly ) {
                    yield return new FieldMember( this.DependencyPropertyKeyFieldName, this.dependencyPropertyKeyTypeName_ ) {
                        Modifiers = "private " + this.StandardDependencyPropertyModifiers,
                        SpaceOutput = true,
                        Condition = CompilationSymbol.NotSilverlight.Append( this.Condition )
                    };
                    yield return new FieldMember( this.FieldName, this.FieldTypeName ) {
                        Doc = this.CreateFieldDoc( ),
                        Modifiers = this.FieldModifiers,
                        SpaceOutput = true,
                        Condition = CompilationSymbol.NotSilverlight.Append( this.Condition ),
                        Attributes = { staticGenericMemberAttribute }
                    };

                    yield return new FieldMember( this.DependencyPropertyKeyFieldName, this.FieldTypeName ) {
                        Modifiers = "private " + this.StandardDependencyPropertyModifiers,
                        SpaceOutput = true,
                        Condition = CompilationSymbol.Silverlight.Append( this.Condition )
                    };
                    yield return new FieldMember( this.FieldName, this.FieldTypeName ) {
                        Modifiers = "internal " + this.StandardDependencyPropertyModifiers,
                        SpaceOutput = true,
                        Condition = CompilationSymbol.Silverlight.Append( this.Condition )
                    };
                }
                else {
                    yield return new FieldMember( this.FieldName, this.FieldTypeName ) {
                        Doc = this.CreateFieldDoc( ),
                        Modifiers = this.FieldModifiers,
                        SpaceOutput = true,
                        Condition = CompilationSymbol.None,
                        Attributes = { staticGenericMemberAttribute }
                    };
                }
            }
        }

        /// <inheritdoc/>
        protected override AccessorMember Accessor {
            get {
                if( this.Attached )
                    return null;

                var accessor = new AccessorMember( this.AccessorName, this.TypeName ) {
                    Doc = this.CreateAccessorDoc( ),
                    Modifiers = this.AccessorModifiers,
                    ObserveMember = this.CreateAccessorBlock(
                        ObserveAccessorName,
                        string.Format( "return ({0})this.GetValue({1});", this.TypeName, this.DependencyPropertyObserveName ) ),
                    UpdateMember = this.CreateAccessorBlock(
                        (this.SetterAccessibility > this.Accessibility ? GetAccessibilityValue( this.SetterAccessibility ) + " " : "") + UpdateAccessorName,
                        string.Format( "this.SetValue({0}, value);", this.DependencyPropertyUpdateName )
                    )
                };

                if( this.IsSilverlightShimRequired ) {
                    var silverlightArgs = this.BuildRegistrationArguments( true );
                    var silverlightShim = this.CreateChangedCallbackShim( silverlightArgs );
                    accessor.Helpers.Add( silverlightShim );

                    if( this.IsSilverlightFreezableHelperRequired ) {
                        var silverlightFreezableHelper = this.CreateSilverlightFreezableHelper( silverlightArgs.ChangedHandler );
                        accessor.Helpers.Add( silverlightFreezableHelper );
                    }

                    if( this.IsSilverlightRevertingValueFieldRequired ) {
                        string argsType = this.Parent.TypeRepository.GetTypeName( typeof( bool ) );
                        var revertingValueField = new FieldMember( this.SilverlightRevertingValueFieldName, argsType ) { Condition = CompilationSymbol.Silverlight };
                        accessor.Helpers.Add( revertingValueField );
                    }
                }

                if( this.IsChangedCallbackShimRequired ) {
                    var changedCallbackArgs = this.BuildRegistrationArguments( false );
                    var changedCallbackShim = this.CreateChangedCallbackShim( changedCallbackArgs );
                    if( !this.IsSilverlightShimRequired )
                        changedCallbackShim.Condition = CompilationSymbol.None;
                    accessor.Helpers.Add( changedCallbackShim );

                    if( this.Changed == Changed.Virtual ) {
                        var virtualChangedMethod = this.CreateVirtualChangedMethod( );
                        accessor.Helpers.Add( virtualChangedMethod );
                    }
                }

                return accessor;
            }
        }

        /// <summary>
        /// Gets the accessors for an attached dependency property, or an empty enumeration if it is not an attached dependency property.
        /// </summary>
        protected IEnumerable<MethodMember> AttachedAccessors {
            get {
                if( !this.Attached )
                    yield break;

                yield return this.CreateAttachedAccessor( true );
                yield return this.CreateAttachedAccessor( false );

                if( this.IsSilverlightShimRequired ) {
                    var silverlightArgs = this.BuildRegistrationArguments( true );
                    yield return this.CreateChangedCallbackShim( silverlightArgs );
                }

                Ensure.Satisfies( !this.IsChangedCallbackShimRequired, "{0} dependency property changed callbacks are not supported on attached properties.", this.Changed );
            }
        }


        /// <inheritdoc/>
        protected DependencyProperty( PartialType parent, string name, string description )
            : base( parent, name, description ) {
            var typeRepository = this.Parent.TypeRepository;

            this.changeCallbackName_ = typeRepository.GetTypeName( typeof( PropertyChangedCallback ) );
            this.coerceCallbackName_ = typeRepository.GetTypeName( typeof( CoerceValueCallback ) );
            this.validateCallbackName_ = typeRepository.GetTypeName( typeof( ValidateValueCallback ) );

            this.dependencyPropertyTypeName_ = typeRepository.GetTypeName( typeof( DependencyProperty ) );
            this.dependencyPropertyKeyTypeName_ = typeRepository.GetTypeName( typeof( DependencyPropertyKey ) );
        }


        /// <summary>
        /// Assigns the value used for the <see cref="Attached"/> property.
        /// </summary>
        public DependencyProperty<T> SetAttached( bool attached ) {
            this.Attached = attached;
            return this;
        }

        /// <summary>
        /// Assigns the value used for the <see cref="Attached"/> property.
        /// </summary>
        public DependencyProperty<T> SetAttached( Type targetType ) {
            this.AttachedTargetType = targetType;
            this.Attached = targetType != null;
            return this;
        }

        /// <summary>
        /// Assigns the value used for the <see cref="SetterAccessibility"/> property.
        /// </summary>
        [Category( "Read-Only" )]
        public DependencyProperty<T> SetReadOnly( bool readOnly ) {
            Accessibility setterAccessibility = readOnly
                ? Accessibility.Private
                : Accessibility.Public;
            SetSetterAccessibility( setterAccessibility );
            return this;
        }

        /// <summary>
        /// Assigns the value used for the <see cref="SetterAccessibility"/> property.
        /// </summary>
        [Category( "Read-Only" )]
        public DependencyProperty<T> SetSetterAccessibility( Accessibility setterAccessibility ) {
            this.SetterAccessibility = setterAccessibility;
            return this;
        }


        /// <summary>Assigns the valued used for the <see cref="Options"/> property on Desktop and Silverlight.</summary>
        [EditorBrowsable( EditorBrowsableState.Advanced )]
        public DependencyProperty<T> SetOptions( FrameworkPropertyMetadataOptions options, string slOverride ) {
            this.Options = options;
            this.SilverlightOptionsOverride = slOverride;
            return this;
        }

        /// <summary>Assigns the valued used for the <see cref="Options"/> property.</summary>
        [DefaultValue( "None,AffectsMeasure,AffectsArrange,AffectsRender,AffectsParentMeasure,AffectsParentArrange" )]
        public DependencyProperty<T> SetOptions( FrameworkPropertyMetadataOptions options ) { return this.SetOptions( options, null ); }

        /// <summary>Assigns the valued used for the <see cref="Coerce"/> property.</summary>
        public DependencyProperty<T> SetCoerce( Coerce coerce ) { this.Coerce = coerce; return this; }

        /// <summary>Assigns the valued used for the <see cref="Validate"/> property.</summary>
        public virtual DependencyProperty<T> SetValidate( Validate validate ) { this.Validate = validate; return this; }

        /// <summary>Assigns the valued used for the <see cref="Changed"/> property.</summary>
        public DependencyProperty<T> SetChanged( Changed changed ) {
            this.Changed = changed;
            if( this.Changed == Changed.Virtual || this.Changed == Changed.Instance || this.Changed == Changed.Parameterless )
                this.ChangedHandler = "On" + this.ChangedAction;
            return this;
        }

        /// <summary>Assigns the valued used for the <see cref="Coerce"/> property.</summary>
        public DependencyProperty<T> SetCoerce( string coerceHandler ) {
            this.Coerce = Coerce.Custom;
            this.CoerceHandler = coerceHandler;
            return this;
        }

        /// <summary>Assigns the valued used for the <see cref="Validate"/> property.</summary>
        public virtual DependencyProperty<T> SetValidate( string validateHandler ) {
            this.Validate = Validate.Custom;
            this.ValidateHandler = validateHandler;
            return this;
        }

        /// <summary>Assigns the valued used for the <see cref="Changed"/> property.</summary>
        public DependencyProperty<T> SetChanged( string changedHandler ) {
            this.Changed = Changed.Static;
            this.ChangedHandler = changedHandler;
            return this;
        }


        /// <inheritdoc/>
        protected override IEnumerable<Member> GetMembers( ) {
            foreach( var field in this.Fields )
                yield return field;

            foreach( var initializer in this.Initializers )
                yield return initializer;

            var accessor = this.Accessor;
            if( accessor != null ) {
                accessor.AddAttributes( this.Attributes );
                yield return accessor;
            }

            foreach( var attached in this.AttachedAccessors )
                yield return attached;
        }

        /// <inheritdoc/>
        public override void Prepare( ) {
            base.Prepare( );

            if( this.AffectsParent )
                this.Parent.TypeRepository.AddNamespace( typeof( VisualTreeHelper ) );

            if( this.Attached )
                this.Parent.TypeRepository.AddNamespace( this.AttachedTargetType );
        }

        /// <inheritdoc/>
        protected virtual Doc CreateFieldDoc( ) {
            string propertyKind = this.Attached ? "attached" : "dependency";
            string typeFullName = Doc.GetDocTypeName( this.Parent.TypeFullName, false );
            Doc doc = this.CreateDoc( this.FieldName )
                .AddSummary( "Identifies the <see cref='P:{0}.{1}'/> {2} property.", typeFullName, this.AccessorName, propertyKind )
                .AddReturns( "The identifier for the <see cref='P:{0}.{1}'/> {2} property.", typeFullName, this.AccessorName, propertyKind );
            if( this.Attached && this.Description == null )
                doc.UseIncludeTag( ).UseDocMembers( );
            return doc;
        }

        /// <inheritdoc/>
        protected virtual Doc CreateAccessorDoc( ) {
            string settableDescription = this.IsReadOnly ? "Gets {0}." : "Gets or sets {0}.";
            return this.CreateDoc( )
                .AddSummary( settableDescription, this.Description )
                .AddExceptions( this.Guards );
        }

        /// <summary>
        /// Creates arguments for registering a dependency property.
        /// </summary>
        protected DependencyPropertyMetadata BuildRegistrationArguments( bool silverlight ) {
            var args = new DependencyPropertyMetadata( this.Parent.TypeRepository, silverlight );


            // Default value
            string defaultValue = this.GetDefaultValueForMetadata( silverlight );
            args.AddMetadataArgument( defaultValue, DependencyPropertyMetadataKind.Base );

            // Freezable validation
            if( this.IsParentFreezable )
                args.UsedExtensions |= Extensions.Validation;
            if( this.IsSilverlightStyleFreezeHelperRequired || this.IsSilverlightFreezableHelperRequired || defaultValue.Contains( FreezableImplementer.SafeFreezeHelperName ) )
                args.UsedExtensions |= Extensions.Freezable;

            // Meatadata options
            if( !silverlight && this.Options != FrameworkPropertyMetadataOptions.None )
                args.AddMetadataArgument( this.Parent.TypeRepository.GetValueString( this.Options, false ), DependencyPropertyMetadataKind.Framework );


            // Change and Coerce callbacks
            const string CustomCallbackFormat = "new {0}({2}.{1})";
            const string InstanceShimCustomCallbackFormat = "{1}";
            const string SilverlightShimCustomCallbackFormat = "{2}.{1}";

            bool useSilverlightShim = silverlight && this.IsSilverlightShimRequired;
            bool useChangedCallbackShim = this.IsChangedCallbackShimRequired;
            bool useShim = useSilverlightShim || useChangedCallbackShim;
            if( useShim ) {
                string changedCallbackShim = this.CreateCallbackString( CustomCallbackFormat, this.changeCallbackName_, this.ShimChangedAction );
                args.AddMetadataArgument( changedCallbackShim, DependencyPropertyMetadataKind.Base );
            }

            string changedHandlerFormat = null;
            if( useChangedCallbackShim )
                changedHandlerFormat = InstanceShimCustomCallbackFormat;
            else if( this.Changed != Changed.None )
                changedHandlerFormat = useSilverlightShim ? SilverlightShimCustomCallbackFormat : CustomCallbackFormat;
            else if( this.Coerce != Coerce.None )
                changedHandlerFormat = silverlight ? null : "({0})null";
            args.ChangedHandler = this.CreateCallbackString( changedHandlerFormat, this.changeCallbackName_, this.ChangedHandler ?? this.ChangedAction );
            if( !useShim )
                args.AddMetadataArgument( args.ChangedHandler, DependencyPropertyMetadataKind.Base );

            string coerceHandlerFormat = null;
            if( this.Coerce == Coerce.Custom )
                coerceHandlerFormat = silverlight ? SilverlightShimCustomCallbackFormat : CustomCallbackFormat;
            else if( this.Coerce == Coerce.NonNegative ) {
                const string CoerceNonNegative = "DependencyPropertyCallbacks.CoerceNonNegative<{3}>";
                coerceHandlerFormat = silverlight ? CoerceNonNegative : "new {0}(" + CoerceNonNegative + ")";
                args.UsedExtensions |= Extensions.DependencyProperties;
            }
            args.CoerceHandler = this.CreateCallbackString( coerceHandlerFormat, this.coerceCallbackName_, this.CoerceHandler ?? this.CoerceAction );
            if( !silverlight )
                args.AddMetadataArgument( args.CoerceHandler, DependencyPropertyMetadataKind.UI );


            // Validate callback
            string validateHandlerFormat = null;
            if( this.Validate == Validate.Custom )
                validateHandlerFormat = silverlight ? SilverlightShimCustomCallbackFormat : CustomCallbackFormat;
            else if( this.Validate == Validate.Enum ) {
                const string IsValidEnum = "DependencyPropertyCallbacks.IsValidEnum<{3}>";
                validateHandlerFormat = silverlight ? IsValidEnum : "new {0}(" + IsValidEnum + ")";
            }
            else if( this.Validate == Validate.NotNull ) {
                const string IsNotNull = "DependencyPropertyCallbacks.IsNotNull<{3}>";
                validateHandlerFormat = silverlight ? IsNotNull : "new {0}(" + IsNotNull + ")";
            }
            args.ValidationHandler = this.CreateCallbackString( validateHandlerFormat, this.validateCallbackName_, this.ValidateHandler ?? this.ValidateAction );
            if( !string.IsNullOrEmpty( args.ValidationHandler ) ) {
                args.UsedExtensions |= Extensions.DependencyProperties;
                if( silverlight )
                    args.UsedExtensions |= Extensions.Validation;
            }


            return args;
        }

        protected bool InitializeDefaultValueInConstructor( string defaultValue ) {
            // Default values of readonly properties created with "new" will be initialized in constructor instead of metadata.
            return this.IsReadOnly
                && defaultValue != null
                && defaultValue.StartsWith( "new " );
        }


        private bool Flags( FrameworkPropertyMetadataOptions option ) {
            return (this.Options & option) == option;
        }

        private string GetDefaultValueForMetadata( bool silverlight ) {
            string assignedDefaultValue;
            if( !this.DefaultValues.TryGetValue( CompilationSymbol.None, out assignedDefaultValue ) ) {
                var key = silverlight ? CompilationSymbol.Silverlight : CompilationSymbol.NotSilverlight;
                this.DefaultValues.TryGetValue( key, out assignedDefaultValue );
            }

            string metadataDefaultValue;
            if( string.IsNullOrEmpty( assignedDefaultValue ) || this.InitializeDefaultValueInConstructor( assignedDefaultValue ) )
                metadataDefaultValue = "default(" + this.TypeName + ")";
            else {
                if( assignedDefaultValue.StartsWith( "new " ) || assignedDefaultValue.StartsWith( this.TypeName ) )
                    metadataDefaultValue = assignedDefaultValue;
                else
                    metadataDefaultValue = "(" + this.TypeName + ")" + assignedDefaultValue;

                if( silverlight && this.IsSilverlightPropertyFreezeHelperRequired && assignedDefaultValue != "null" && !assignedDefaultValue.Contains( "SafeFreeze(" ) )
                    metadataDefaultValue = "(" + metadataDefaultValue + ").SafeFreeze()";
            }

            return metadataDefaultValue;
        }

        private string CreateCallbackString( string format, string callbackTypeName, string actionName ) {
            if( format == null )
                return null;

            if( actionName.Contains( '.' ) )
                format = format.Replace( "{2}.", "" );

            return string.Format( format, callbackTypeName, actionName, this.Parent.TypeNamePrefix, this.TypeName );
        }

        private MethodMember CreateVirtualChangedMethod( ) {
            var minimumChangedMethod = typeof( System.Windows.Controls.Primitives.RangeBase ).GetMethod( "OnMinimumChanged", BindingFlags.NonPublic | BindingFlags.Instance );
            string documentedMember = minimumChangedMethod.DeclaringType.FullName + ".Minimum";
            string typeNamePrefix = Doc.GetDocTypeName( this.Parent.TypeNamePrefix );
            var parameters = MethodMember.GetParameters( minimumChangedMethod, ( name, type, description ) => {
                var parameter = this.CreateParameter( name.Replace( "Minimum", "Value" ), this.TypeName, description );
                parameter.Doc.AddReplacement( documentedMember, "{0}.{1}", typeNamePrefix, this.AccessorName );
                return parameter;
            } );

            var docMember = XmlDocumentation.GetDocMember( minimumChangedMethod );
            Doc doc = this.CreateDoc( )
                .AddReplacement( documentedMember, "{0}.{1}", typeNamePrefix, this.AccessorName )
                .AddDocElements( XmlDocumentation.FilterElements( docMember, "param" ) );

            var virtualChangedHelper = new MethodMember( this.ChangedHandler, "void", null );
            var virtualChangedCallbackBody = new BlockMember( this.ChangedHandler, w => w.WriteLine( "this.{0}();", this.ChangedHandler ) );
            return new MethodMember( this.ChangedHandler, "void", virtualChangedCallbackBody ) {
                Modifiers = "protected virtual",
                Doc = doc,
                Helpers = { virtualChangedHelper }
            }.AddParameters( parameters );
        }

        private MethodMember CreateChangedCallbackShim( DependencyPropertyMetadata args ) {
            var propertyChangedMethod = typeof( PropertyChangedCallback ).GetMethod( "Invoke" );
            var parameters = MethodMember.GetParameters( propertyChangedMethod, ( name, type, description ) => {
                var parameter = this.CreateParameter( name, type, "empty description of " + name );
                parameter.Doc = null;
                return parameter;
            } ).ToArray( );
            string objectName = parameters[0].Name;
            string changedArgsName = parameters[1].Name;
            string targetName = this.Attached ? objectName : "self";

            var shimPropertyChangedBody = new BlockMember( this.ShimChangedAction, w => {
                bool writeTarget = !this.Attached;
                bool writeCoerce = args.Silverlight && args.CoerceHandler != null;
                bool writeValidate = args.Silverlight && args.ValidationHandler != null;
                bool writeChanged = args.ChangedHandler != null;
                bool writeRevertingValueField = args.Silverlight && this.IsSilverlightRevertingValueFieldRequired;
                bool writeStyleFreezeHelper = args.Silverlight && this.IsSilverlightStyleFreezeHelperRequired;
                bool writeFreezableHelper = args.Silverlight && this.IsSilverlightFreezableHelperRequired;
                bool writeParentFreezableHelpers = args.Silverlight && this.IsParentFreezable;
                bool writeMetadataHelpers = args.Silverlight && this.IsSilverlightMetadataHelperRequired;

                Action revert = ( ) => {
                    if( !this.Attached )
                        w.WriteLine( "{0}.{1} = true;", targetName, this.SilverlightRevertingValueFieldName );
                    w.WriteLine( "{0}.SetValue({1}, {2}.OldValue);", targetName, this.DependencyPropertyUpdateName, changedArgsName );
                    if( !this.Attached )
                        w.WriteLine( "{0}.{1} = false;", targetName, this.SilverlightRevertingValueFieldName );
                };

                Action assignTarget = ( ) =>
                    w.WriteLine( "{0} {1} = ({0}){2};", this.Parent.TypeName, targetName, objectName );
                Action handleRevertingValue = ( ) => {
                    w.WriteLine( "if ({0}.{1})", targetName, this.SilverlightRevertingValueFieldName );
                    using( Enclose.Indent( w ) )
                        w.WriteLine( "return;" );
                };
                Action handleFrozenParent = ( ) => {
                    w.WriteLine( "if ({0}.IsFrozen)", targetName );
                    using( Enclose.Braces( w ) ) {
                        revert( );
                        w.WriteLine( "{0}.GuardValue(\"this\").Satisfies(false, \"Cannot set a property on object '{{0}}' because it is in a read-only state.\", {0});", targetName );
                    }
                };

                Action assignValue = ( ) =>
                    w.WriteLine( "object value = {0}.NewValue;", changedArgsName );
                Action coerce = ( ) => {
                    w.WriteLine( "object coercedValue = {0}({1}, value);", args.CoerceHandler, objectName );
                    w.WriteLine( "if (!object.Equals(coercedValue, value))" );
                    using( Enclose.Braces( w ) ) {
                        w.WriteLine( "{0}.SetValue({1}, coercedValue);", targetName, this.DependencyPropertyUpdateName );
                        w.WriteLine( "return;" );
                    }
                };
                Action validate = ( ) => {
                    w.WriteLine( "bool isValid = {0}(value);", args.ValidationHandler );
                    w.WriteLine( "if (!isValid)" );
                    using( Enclose.Braces( w ) ) {
                        revert( );
                        w.WriteLine( "value.GuardParam(\"value\").Satisfies(false, \"'{{0}}' is not a valid value for property '{0}'.\", value);", this.AccessorName );
                    }
                };

                Action updateStyleFreezeHelpers = ( ) => {
                    w.WriteLine( "object localValue = self.ReadLocalValue({0});", this.FieldName );
                    w.WriteLine( "if (object.ReferenceEquals(localValue, DependencyProperty.UnsetValue))" );
                    using( Enclose.Indent( w ) )
                        w.WriteLine( "{0}.NewValue.SafeFreeze();", changedArgsName );
                };
                Action updateFreezableHelpers = ( ) => {
                    w.WriteLine( "{0}.OldValue.SafeSubpropertyChanged({1}.{2}, false);", changedArgsName, targetName, this.SilverlightSubpropertyChangedAction );
                    w.WriteLine( "{0}.NewValue.SafeSubpropertyChanged({1}.{2}, true);", changedArgsName, targetName, this.SilverlightSubpropertyChangedAction );
                };

                Action changed = ( ) => {
                    if( this.Changed == Changed.Parameterless )
                        w.WriteLine( "{0}.{1}();", targetName, args.ChangedHandler );
                    else if( this.IsChangedCallbackShimRequired )
                        w.WriteLine( "{0}.{1}(({2}){3}.OldValue, ({2}){3}.NewValue);", targetName, args.ChangedHandler, this.TypeName, changedArgsName );
                    else
                        w.WriteLine( "{0}({1}, {2});", args.ChangedHandler, targetName, changedArgsName );
                };
                Action parentFreezableHelpers = ( ) =>
                    w.WriteLine( "{0}.OnSubpropertyChanged();", targetName );
                Action metadataHelpers = ( ) => {
                    if( this.SilverlightOptionsOverride != null )
                        w.WriteLine( "{0}({1}, {2});", this.SilverlightOptionsOverride, targetName, changedArgsName );
                    else
                        this.WriteSilverlightMetadataOptions( w, targetName );
                };


                var body = new[] {
                    new { Write = assignTarget,                 Enable = writeTarget,                   Separate = false },
                    new { Write = handleRevertingValue,         Enable = writeRevertingValueField,      Separate = true  },
                    new { Write = handleFrozenParent,           Enable = writeParentFreezableHelpers,   Separate = true  },
                    new { Write = assignValue,                  Enable = writeCoerce || writeValidate,  Separate = false },
                    new { Write = coerce,                       Enable = writeCoerce,                   Separate = true  },
                    new { Write = validate,                     Enable = writeValidate,                 Separate = true  },
                    new { Write = updateStyleFreezeHelpers,     Enable = writeStyleFreezeHelper,        Separate = true  },
                    new { Write = updateFreezableHelpers,       Enable = writeFreezableHelper,          Separate = true  },
                    new { Write = changed,                      Enable = writeChanged,                  Separate = false },
                    new { Write = parentFreezableHelpers,       Enable = writeParentFreezableHelpers,   Separate = false },
                    new { Write = metadataHelpers,              Enable = writeMetadataHelpers,          Separate = false },
                };

                bool separate = false;
                foreach( var entry in body ) {
                    if( !entry.Enable )
                        continue;

                    if( separate )
                        w.WriteLine( );
                    separate = entry.Separate;

                    entry.Write( );
                }
            } );

            return new MethodMember( this.ShimChangedAction, "void", shimPropertyChangedBody ) {
                Modifiers = "private static",
                Condition = args.Silverlight ? CompilationSymbol.Silverlight : CompilationSymbol.NotSilverlight
            }.AddParameters( parameters );
        }

        private MethodMember CreateSilverlightFreezableHelper( string changedHandler ) {
            var subpropertyChangedMethod = typeof( EventHandler ).GetMethod( "Invoke" );
            var parameters = MethodMember.GetParameters( subpropertyChangedMethod, ( name, type, description ) => {
                var parameter = this.CreateParameter( name, type, "empty description of " + name );
                parameter.Doc = null;
                return parameter;
            } ).ToArray( );

            var silverlightSubpropertyChangedBody = new BlockMember( this.SilverlightSubpropertyChangedAction, w => {
                if( this.Changed == Changed.Parameterless ) {
                    w.WriteLine( "this.{0}();", changedHandler );
                }
                else if( this.IsChangedCallbackShimRequired ) {
                    w.WriteLine( "{0} value = this.{1};", this.TypeName, this.AccessorName );
                    w.WriteLine( "this.{0}(value, value);", changedHandler );
                }
                else if( changedHandler != null ) {
                    w.WriteLine( "{0}(this, null);", changedHandler, typeof( DependencyPropertyChangedEventArgs ).Name );
                }

                if( this.IsParentFreezable )
                    w.WriteLine( "this.OnSubpropertyChanged();" );
            } );

            return new MethodMember( this.SilverlightSubpropertyChangedAction, "void", silverlightSubpropertyChangedBody ) {
                Modifiers = "private",
                Condition = CompilationSymbol.Silverlight
            }.AddParameters( parameters );
        }

        private void WriteSilverlightMetadataOptions( ICodeWriter writer, string targetName ) {
            foreach( FrameworkPropertyMetadataOptions option in Enum.GetValues( typeof( FrameworkPropertyMetadataOptions ) ) ) {
                bool parent = false;
                string methodName = null;

                switch( this.Options & option ) {
                    case FrameworkPropertyMetadataOptions.AffectsParentMeasure: { parent = true; goto case FrameworkPropertyMetadataOptions.AffectsMeasure; }
                    case FrameworkPropertyMetadataOptions.AffectsMeasure: { methodName = "InvalidateMeasure"; break; }

                    case FrameworkPropertyMetadataOptions.AffectsParentArrange: { parent = true; goto case FrameworkPropertyMetadataOptions.AffectsArrange; }
                    case FrameworkPropertyMetadataOptions.AffectsArrange: { methodName = "InvalidateArrange"; break; }

                    case FrameworkPropertyMetadataOptions.AffectsRender: { methodName = "InvalidateRender"; break; }

                    case FrameworkPropertyMetadataOptions.BindsTwoWayByDefault:
                    case FrameworkPropertyMetadataOptions.Inherits:
                    case FrameworkPropertyMetadataOptions.Journal:
                    case FrameworkPropertyMetadataOptions.NotDataBindable:
                    case FrameworkPropertyMetadataOptions.OverridesInheritanceBehavior:
                    case FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender:
                        throw new NotSupportedException( string.Format( "{0} metadata flag value is not supported in silverlight.", option ) );

                    case FrameworkPropertyMetadataOptions.None:
                    default:
                        break;
                }

                if( methodName != null ) {
                    string instance = parent ? "    parent" : this.Attached ? "    target" : targetName;
                    if( parent ) {
                        writer.WriteLine( "UIElement parent = VisualTreeHelper.GetParent({0}) as UIElement;", targetName );
                        writer.WriteLine( "if (!object.ReferenceEquals(parent, null))" );
                    }
                    else if( this.Attached ) {
                        writer.WriteLine( "UIElement target = {0} as UIElement;", targetName );
                        writer.WriteLine( "if (!object.ReferenceEquals(target, null))" );
                    }

                    writer.WriteLine( "{0}.{1}();", instance, methodName );
                }
            }
        }

        private MethodMember CreateAttachedAccessor( bool getAccessor ) {
            string returnType = getAccessor ? this.TypeName : "void";
            string accessorPrefix = getAccessor ? "Get" : "Set";
            string accessorName = accessorPrefix + this.AccessorName;
            string accessibility = (getAccessor || !this.IsReadOnly ? this.AccessorModifiers : GetAccessibilityValue( this.SetterAccessibility )) + " ";

            // Use DockPanel's GetDock/SetDock methods as prototype for attached methods.
            Type dockPanelType = typeof( DockPanel );
            Type dockPropertyType = typeof( Dock );
            string dockPropertyName = dockPropertyType.Name;
            var accessorMember = dockPanelType.GetMethod( accessorPrefix + dockPropertyName );
            var parameters = MethodMember.GetParameters( accessorMember, ( name, type, description ) => {
                if( type == dockPropertyType ) { name = "value"; type = this.Type; }
                else { type = this.AttachedTargetType; }
                var parameter = this.CreateParameter( name, type, description );
                parameter.Doc.AddReplacement( dockPropertyType.FullName + '"', "{0}\"", Doc.GetDocTypeName( this.TypeName ) );
                return parameter;
            } );
            var targetParameter = parameters.First( );
            string targetName = targetParameter.Name;

            // Create the appropriate accessor body.
            string dependencyPropertyName = getAccessor ? this.DependencyPropertyObserveName : this.DependencyPropertyUpdateName;
            string accessorBodyFormat = getAccessor ? "return ({0}){1}.GetValue({2});" : "{1}.SetValue({2}, value);";
            var accessorBody = new BlockMember( accessorName, string.Format( accessorBodyFormat, this.TypeName, targetName, dependencyPropertyName ) );
            accessorBody.AddGuards( new[] { Guard.NotNull( this.CreateDoc( ), targetName, forceDescription: true ) } );

            // Get the prototype documentation.
            var accessorDocMember = XmlDocumentation.GetDocMember( accessorMember );
            Doc doc = this.CreateDoc( )
                .AddReplacement( dockPanelType.FullName + "." + dockPropertyName, "P:{0}.{1}", Doc.GetDocTypeName( this.Parent.TypeFullName, false ), this.AccessorName )
                .AddDocElements( XmlDocumentation.FilterElements( accessorDocMember, "param" ) )
                .UseDocMembers( onlyDocMembers: true );
            if( this.Description != null )
                doc.AddDocElement( "remarks", "{0}s {1}.", accessorPrefix, this.Description );
            return new MethodMember( accessorName, returnType, accessorBody ) {
                Doc = doc,
                Modifiers = accessibility + "static"
            }.AddParameters( parameters );
        }


        #region FreezableImplementer.ITarget Members

        bool FreezableImplementer.ITarget.Freezable {
            get { return this.IsPropertyFreezable; }
        }

        string FreezableImplementer.ITarget.PropertyName {
            get { return this.AccessorName; }
        }

        void FreezableImplementer.ITarget.Prepare( FreezableImplementer implementer ) {
            this.IsParentFreezable = !this.Attached;
        }

        #endregion
    }

}
